#include "push_execute.h"

void (*ori_execute_ex)(zend_op_array *op_array TSRMLS_DC) = nullptr;
void (*ori_execute_internal)(zend_execute_data *execute_data_ptr, int return_value_used TSRMLS_DC) = nullptr;

// curl
extern void push_curl_init_handler(zend_execute_data *execute_data, long execute_micro_time, string class_name, string function_name);
extern void push_curl_setopt_handler(zend_execute_data *execute_data, long execute_micro_time, string class_name, string function_name);
extern void push_curl_setopt_array_handler(zend_execute_data *execute_data, long execute_micro_time, string class_name, string function_name);
extern void push_curl_exec_handler(zend_execute_data *execute_data, long execute_micro_time, string class_name, string function_name);
extern void push_curl_close_handler(zend_execute_data *execute_data, long execute_micro_time, string class_name, string function_name);
// pdo
extern void push_pdo_handler(zend_execute_data *execute_data_ptr, long execute_micro_time, string class_name, string function_name);
extern void push_pdo_stmt_handler(zend_execute_data *execute_data_ptr, long execute_micro_time, string class_name, string function_name);
// mysqli
extern void push_mysqli_handler(zend_execute_data *execute_data, long execute_micro_time, string class_name, string function_name);
extern void push_mysqli_obj_handler(zend_execute_data *execute_data, long execute_micro_time, string class_name, string function_name);
extern void push_mysqli_stmt_handler(zend_execute_data *execute_data, long execute_micro_time, string class_name, string function_name);
extern void push_mysqli_stmt_obj_handler(zend_execute_data *execute_data, long execute_micro_time, string class_name, string function_name);
// phpredis
extern void push_phpredis_handler(zend_execute_data *execute_data, long execute_micro_time, string class_name, string function_name);

typedef void (*internal_func)(zend_execute_data *, long, string, string);
map<string, internal_func> exec_internal_func = {
    // curl
    {"null::curl_init", push_curl_init_handler},
    {"null::curl_setopt", push_curl_setopt_handler},
    {"null::curl_setopt_array", push_curl_setopt_array_handler},
    {"null::curl_exec", push_curl_exec_handler},
    {"null::curl_close", push_curl_close_handler},
    // pdo
    {"PDO::query", push_pdo_handler},
    {"PDO::exec", push_pdo_handler},
    {"PDO::prepare", push_pdo_handler},
    {"PDOStatement::execute", push_pdo_stmt_handler},
    // mysqli
    {"null::mysqli_query", push_mysqli_handler},
    {"null::mysqli_prepare", push_mysqli_handler},
    {"mysqli::prepare", push_mysqli_obj_handler},
    {"null::mysqli_stmt_execute", push_mysqli_stmt_handler},
    {"mysqli_stmt::execute", push_mysqli_stmt_obj_handler},
    // phpredis
    {"Redis::*", push_phpredis_handler},
};

// predis
extern void push_predis_handler(zend_execute_data *execute_data, long execute_micro_time, string class_name, string function_name);

typedef void (*ex_func)(zend_execute_data *, long, string, string);
map<string, ex_func> exec_ex_func = {
    // predis
    {"Predis\\Client::__call", push_predis_handler},
};

void push_db_data(string addr, string query, string error, long execute_micro_time)
{
    string bucket = PUSHGATEWAY_G(bucket);

    client *pushgateway_client = client::newInstance();
    scaler *sc_total = new scaler;
    scaler *sc_second = new scaler;

    if (error == "")
    {
        sc_total->setIndex(INDEX_SQL_USEGE_TOTAL);
        sc_total->addItem(ITEM_SQL_ADDR, addr);
        sc_total->addItem(ITEM_SQL_NAME, query);
        pushgateway_client->setCounter(sc_total);
    }
    else
    {
        sc_total->setIndex(INDEX_SQL_USEGE_ERROR_TOTAL);
        sc_total->addItem(ITEM_SQL_ADDR, addr);
        sc_total->addItem(ITEM_SQL_NAME, query);
        sc_total->addItem(ITEM_SQL_ERROR, error);
        pushgateway_client->setCounter(sc_total);
    }

    sc_second->setIndex(INDEX_SQL_USEGE_DURATION_S);
    sc_second->addItem(ITEM_SQL_ADDR, addr);
    sc_second->addItem(ITEM_SQL_NAME, query);
    pushgateway_client->setBucket(sc_second, execute_micro_time * pow(0.1, PUSH_HISTOGRAM_DECIAML), bucket);
}

void push_execute_internal(zend_execute_data *execute_data_ptr, int return_value_used TSRMLS_DC)
{
    struct timeval tv;
    long execute_micro_time = 0, start_micro_time, end_micro_time;
    gettimeofday(&tv, NULL);
    start_micro_time = tv.tv_sec * 1000000 + tv.tv_usec;

    if (ori_execute_internal)
    {
        ori_execute_internal(execute_data_ptr, return_value_used);
    }
    else
    {
        execute_internal(execute_data_ptr, return_value_used);
    }

    gettimeofday(&tv, NULL);
    end_micro_time = tv.tv_sec * 1000000 + tv.tv_usec;
    execute_micro_time = end_micro_time - start_micro_time;

    // 获取类名 和 方法名
    zend_function *func = execute_data_ptr->function_state.function;
    if (func != nullptr)
    {
        int is_class = func->common.scope != nullptr && func->common.scope->name != nullptr;
        const char *class_name = is_class ? func->common.scope->name : nullptr;
        const char *function_name = func->common.function_name != nullptr ? func->common.function_name : nullptr;

        string cn = class_name ? class_name : "null";
        string fn = function_name ? function_name : "null";
        string cf = cn + "::" + fn;
        string cf_all = cn + "::*";

        if (exec_internal_func.count(cf) > 0)
        {
            exec_internal_func[cf](execute_data_ptr, execute_micro_time, cn, fn);
        }
        else if (exec_internal_func.count(cf_all) > 0)
        {
            exec_internal_func[cf_all](execute_data_ptr, execute_micro_time, cn, fn);
        }
    }
}

void push_execute_ex(zend_op_array *op_array TSRMLS_DC)
{
    zend_execute_data *execute_data = nullptr;
    if (EG(current_execute_data))
    {
        execute_data = EG(current_execute_data);
    }

    struct timeval tv;
    long execute_micro_time = 0, start_micro_time, end_micro_time;
    gettimeofday(&tv, NULL);
    start_micro_time = tv.tv_sec * 1000000 + tv.tv_usec;

    ori_execute_ex(op_array);

    gettimeofday(&tv, NULL);
    end_micro_time = tv.tv_sec * 1000000 + tv.tv_usec;
    execute_micro_time = end_micro_time - start_micro_time;

    const char *class_name = op_array->scope != nullptr ? op_array->scope->name : nullptr;
    const char *function_name = op_array->function_name != nullptr ? op_array->function_name : nullptr;
    string cn = class_name ? class_name : "null";
    string fn = function_name ? function_name : "null";
    string cf = cn + "::" + fn;
    string cf_all = cn + "::*";

    // char *cn_c = new char[cn.length() + 1];
    // char *fn_c = new char[fn.length() + 1];
    // strcpy(cn_c, cn.c_str());
    // strcpy(fn_c, fn.c_str());
    // php_printf("call ex: %s -> %s\n", cn_c, fn_c);

    if (exec_ex_func.count(cf) > 0)
    {
        exec_ex_func[cf](execute_data, execute_micro_time, cn, fn);
    }
    else if (exec_ex_func.count(cf_all) > 0)
    {
        exec_ex_func[cf_all](execute_data, execute_micro_time, cn, fn);
    }
}